import * as THREE from "three";
import { Particle } from "./Particle";
import { getSPEMat, randomDirectionInCone, randomRadian, randomRange } from "./util";
import { Shape } from "./shapes/Shape";
import { PointShape } from "./shapes/Point";
import { BoxShape } from "./shapes/Box";
import { SphereShape } from "./shapes/Sphere";
import { rectShape } from "./shapes/rect";
import { BufferGeometryShape } from "./shapes/BufferGeometryShape";



interface EmitterOpt{
    origin: THREE.Vector3, rate: number, life: number, emitterLife: number, volecity: THREE.Vector3, angle: number, size: number, shape?: string, shapeParams?: { [key: string]: number | string },
    angleVolecity?: number   ;
    randomVelocity?: { 
        direction?: THREE.Vector3;     // 主方向
        coneAngle?: number;            // 椎体角度
        speedMin?: number;             // 最小速度
        speedMax?: number;             // 最大速度
    },
    normalVelocity?: {
        enabled?: boolean;             // 是否启用法向速度
        speed: number;                // 固定法向速度大小
        speedRange?: [number, number]; // 随机法向速度范围 [min, max]
    },
    material?: THREE.PointsMaterial;
    maxCount?: number;
    // 支持 BufferGeometry
    geometry?: THREE.BufferGeometry;
}

const defaultConfig : EmitterOpt = {
    origin: new THREE.Vector3(0, 0, 0),
    rate: 100,
    life: 6,
    emitterLife: 10,
    volecity: new THREE.Vector3(0, 0, 1),
    randomVelocity: { 
        direction: new THREE.Vector3(0,0,1),     // 主方向
        coneAngle: 0,            // 椎体角度 0就是没有锥体
        speedMin: 3,             // 最小速度
        speedMax: 20,             // 最大速度
    },
    normalVelocity: {
        enabled: false,          // 默认不启用法向速度
        speed: 1,                // 固定法向速度
        speedRange: [0, 0],      // 随机法向速度范围
    },
    angle: 0,
    size: 1,
    shape: 'point',
    shapeParams: {},
    angleVolecity: 0,
    material: getSPEMat({
        color: new THREE.Color(0xffffff),
        size: 1,
        sizeAttenuation: false,
        vertexColors: true,
        depthTest: true,
        depthWrite: false,
        blending: THREE.AdditiveBlending,
        transparent: true,
        alphaTest: 0.001,
    }),
    maxCount: 1000,
}
export class Emitter {
    origin: THREE.Vector3;
    rate: number;
    life: number;
    particles: Set<Particle>;
    emitterLife: number;
    geometry: THREE.BufferGeometry;
    material: THREE.PointsMaterial;
    initialVelocity: THREE.Vector3;
    initialAngleVelocity: number;
    initialAngle: number;
    initialSize: number;
    reLivePool: Set<Particle>;
    maxCount = 1000;
    points: THREE.Points;
    color = new THREE.Color(0xffffff);
    shape: Shape;
    shapeParams: { [key: string]: number | string } = {};
    randomVelocity: {
         direction?: THREE.Vector3; // 主方向
        // 主方向
        coneAngle?: number; // 椎体角度
        // 椎体角度
        speedMin?: number; // 最小速度
        // 最小速度
        speedMax?: number; // 最大速度
    } ={}
    // 速度随机分布，再叠加一个随机乘数，应该是没必要。
    normalVelocity: {
        enabled?: boolean;             // 是否启用法向速度
        speed: number;                // 固定法向速度大小
        speedRange?: [number, number]; // 随机法向速度范围 [min, max]
    } = {speed:1};
    emitAcc= 0; // 每帧发射粒子数 累加器
    constructor(opts:EmitterOpt) {
        const opt = { 
            ...defaultConfig,
            ...opts
        }
        this.particles = new Set();
        this.geometry = new THREE.BufferGeometry();
        this.origin = opt.origin;
        this.rate = opt.rate;
        this.life = opt.life;
        this.emitterLife = opt.emitterLife;
        this.material = opt.material!;

        this.initialVelocity = opt.volecity;
        this.randomVelocity = opt.randomVelocity!;
        this.normalVelocity = opt.normalVelocity!;
        this.initialAngleVelocity = opt.angleVolecity!;
        this.initialAngle = opt.angle;
        this.initialSize = opt.size;

        this.reLivePool = new Set();
        this.maxCount =Math.max(opt.maxCount!, opt.life * opt.rate * 2);
        this.initGeometry();
        this.points = new THREE.Points(this.geometry, this.material);
        this.points.frustumCulled = false;
        opt.shapeParams && (this.shapeParams = opt.shapeParams);
        this.shape = this.createShape(opt.shape!, opt.shapeParams || {}, opt.geometry);
    }

    private createShape(shapeType: string, params: { [key: string]: number | string }, geometry?: THREE.BufferGeometry): Shape {
        switch(shapeType) {
            case 'bufferGeometry':
                return new BufferGeometryShape(this.origin, params, geometry);
            case 'rect':
                return new rectShape(this.origin, params);
            case 'box':
                return new BoxShape(this.origin, params);
            case 'sphere':
                return new SphereShape(this.origin, params);
            case 'point':
            default:
                return new PointShape(this.origin, params);
        }
    }

    initGeometry() {
        const total = this.maxCount;
        const positions = new Float32Array(total * 3);
        const colors = new Float32Array(total * 3);
        const sizes = new Float32Array(total).fill(0);
        const angles = new Float32Array(total);
        let index = 0;
        let particle: Particle;
        for (particle of this.particles) {
            positions[index * 3] = particle.position.x;
            positions[index * 3 + 1] = particle.position.y;
            positions[index * 3 + 2] = particle.position.z;
            colors[index * 3] = particle.color.x;
            colors[index * 3 + 1] = particle.color.y;
            colors[index * 3 + 2] = particle.color.z;
            sizes[index] = particle.size;
            angles[index] = particle.angle;
        }
        this.geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
        this.geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
        this.geometry.setAttribute('a_Size', new THREE.BufferAttribute(sizes, 1));
        this.geometry.setAttribute('a_Angle', new THREE.BufferAttribute(angles, 1));
    }

    emit(deltaTime: number) {
        if (this.emitterLife < 0) {
            return;
        }

        let count = deltaTime * this.rate;
        if (this.emitterLife < deltaTime) {
            count = this.rate * this.emitterLife;
        }
        this.emitAcc += count;
        //  上次剩余的有小数依然累计 直到满1 再次发射
        count = Math.floor(this.emitAcc);
        this.emitAcc -= count;
        
        let particle: Particle, particlePool = [...this.reLivePool];
        const v1 = new THREE.Vector3();
        // 点分布于面上，点分布于体内  ，点在一个矩形面内随机
        /**按现在这种设计，大概没法搞有序的发射了，有序的先不管。 */

        for (let index = 0; index < count; index++) {
            v1.set(0,0,0);
            let pos = this.getOrigin(index)!;
            
            // 1. 首先处理随机锥体速度
            if(this.randomVelocity.coneAngle){
                v1.copy(randomRadian(this.randomVelocity.direction!, this.randomVelocity.coneAngle!));
                v1.multiplyScalar(randomRange(this.randomVelocity.speedMin!, this.randomVelocity.speedMax!));
            }
            
            // 2. 叠加基础初始速度
            v1.add(this.initialVelocity);
            
            // 3. 处理法向速度（如果启用）
            if (this.normalVelocity.enabled) {
                const normal = this.shape.getRandomNormal();
                let normalSpeed = this.normalVelocity.speed;                
                // 计算法向速度大小
                if (this.normalVelocity.speedRange && this.normalVelocity.speedRange[0] !== this.normalVelocity.speedRange[1]) {
                    // 使用随机范围
                    normalSpeed  *= randomRange(this.normalVelocity.speedRange[0], this.normalVelocity.speedRange[1]);
                }
                
                // 叠加法向速度
                const normalVelocity = normal.multiplyScalar(normalSpeed );
                v1.add(normalVelocity);
            }
            
            if (particlePool.length > 0) {
                particle = particlePool.pop()!;
                this.reLivePool.delete(particle);
                particle.reset(pos, v1, this.life, this.initialAngle, this.initialSize);
            } else {
                particle = new Particle(pos, v1, this.life, this.initialAngleVelocity);
            }
            this.particles.add(particle);
        }
    }

    getOrigin(index: number) {
        return this.shape.getRandomPosition();
    }

    update(deltaTime: number) {
        this.emitterLife -= deltaTime;
        this.emit(deltaTime);
        let particles = [...this.particles];
        for (let particle of particles) {
            particle.update(deltaTime);
            if (!particle.alive) {
                this.particles.delete(particle);
                this.reLivePool.add(particle);
            }
        }
        this.updateGeometry();
    }

    updateGeometry() {
        const total = this.particles.size;
        if (total <= this.maxCount) {
            let positions = this.geometry.attributes.position.array as Float32Array;
            let colors = this.geometry.attributes.color.array as Float32Array;
            let sizes = this.geometry.attributes.a_Size.array as Float32Array;
            let angles = this.geometry.attributes.a_Angle.array as Float32Array;
            let particle: Particle, particles = [...this.particles];
            for (let index = 0; index < total; index++) {
                particle = particles[index];
                positions[index * 3] = particle.position.x;
                positions[index * 3 + 1] = particle.position.y;
                positions[index * 3] = particle.position.x;
                positions[index * 3 + 1] = particle.position.y;
                positions[index * 3 + 2] = particle.position.z;
                // 这里写死了xyz,导致没法用color对象了，除非改为toArray，或者定死使用color对象
                colors[index * 3] = particle.color.x;
                colors[index * 3 + 1] = particle.color.y;
                colors[index * 3 + 2] = particle.color.z;
                sizes[index] = particle.size;
                angles[index] = particle.angle;
            }
            this.geometry.setDrawRange(0, total);
            this.geometry.attributes.position.needsUpdate = true;
            this.geometry.attributes.color.needsUpdate = true;
            this.geometry.attributes.a_Size.needsUpdate = true;
            this.geometry.attributes.a_Angle.needsUpdate = true;
        } else {
            // 需要重新生成geometry todo
        }
    }

    reLive(emitterLife: number) {
        this.emitterLife = emitterLife;
    }
}

